/* smallchat.c -- Read clients input, send to all the other connected clients.
 *小聊天程序文件 smallchat.c -- 读取客户端输入，并将其发送给其他所有已连接的客户端。
 *
 * Copyright (c) 2023, Salvatore Sanfilippo <antirez at gmail dot com>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *   * Redistributions of source code must retain the above copyright notice,
 *     this list of conditions and the following disclaimer.
 *   * Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *   * Neither the project name of nor the names of its contributors may be used
 *     to endorse or promote products derived from this software without
 *     specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <assert.h>
#include <sys/select.h>
#include <unistd.h>

#include "chatlib.h"

/* ============================ Data structures =================================
 * The minimal stuff we can afford to have. This example must be simple
 * even for people that don't know a lot of C.
 * 我们所需的最基础的数据结构。这个示例必须足够简单，
 * 即使是不太熟悉 C 语言的人也能理解。
 * =========================================================================== */

 
#define MAX_CLIENTS 1000 // This is actually the higher file descriptor.  // 最大客户端数量，这是实际的最高文件描述符。
#define SERVER_PORT 7711

/* This structure represents a connected client. There is very little
 * info about it: the socket descriptor and the nick name, if set, otherwise
 * the first byte of the nickname is set to 0 if not set.
 * The client can set its nickname with /nick <nickname> command.*/
/* 这个结构体表示一个已连接的客户端。关于它的信息很少：套接字描述符和昵称（如果设置了的话）。
 * 如果昵称未设置，则昵称的第一个字节会被设为 0。
 * 客户端可以使用 /nick <nickname> 命令来设置自己的昵称。 */

struct client {
    int fd;     // Client socket.           // 客户端的套接字描述符
    char *nick; // Nickname of the client. // 客户端的昵称
};

/* This global structure encapsulates the global state of the chat. */
/* 这个全局结构体封装了聊天系统的全局状态。 */
struct chatState {
    int serversock;     // Listening server socket.    // 监听服务器套接字，用于接收客户端的连接请求。 
    int numclients;     // Number of connected clients right now.  // 当前已连接的客户端数量。
    int maxclient;      // The greatest 'clients' slot populated.   // 'clients' 数组中被占用的最大槽位索引。
    struct client *clients[MAX_CLIENTS]; // Clients are set in the corresponding
                                         // slot of their socket descriptor.
    // 客户端数组，客户端会根据其套接字描述符被放置在数组对应的槽位中。
};


struct chatState *Chat; // Initialized at startup. // 全局变量，在程序启动时初始化。

/* ====================== Small chat core implementation ========================
 * Here the idea is very simple: we accept new connections, read what clients
 * write us and fan-out (that is, send-to-all) the message to everybody
 * with the exception of the sender. And that is, of course, the most
 * simple chat system ever possible.
 * =========================================================================== */
 /* 这里的实现非常简单：我们接受新的连接，读取客户端发送给我们的消息，
 * 然后将消息发送给除了发送者之外的所有客户端。当然，这是最
 * 简单的聊天系统，无论从功能还是实现角度来看都是如此。 */

/* Create a new client bound to 'fd'. This is called when a new client
 * connects. As a side effect updates the global Chat state. */
/* 创建一个新的客户端，绑定到 'fd'。这在新客户端连接时被调用。
 * 作为副作用，更新全局 Chat 状态。 */
struct client *createClient(int fd) {
    char nick[32]; // Used to create an initial nick for the user. // 用于为用户创建初始昵称的字符串。
    int nicklen = snprintf(nick,sizeof(nick),"user:%d",fd);
    struct client *c = chatMalloc(sizeof(*c));
    socketSetNonBlockNoDelay(fd); // Pretend this will not fail.// 假设这个函数不会失败。
    c->fd = fd;
    c->nick = chatMalloc(nicklen+1);
    memcpy(c->nick,nick,nicklen);
    assert(Chat->clients[c->fd] == NULL); // This should be available. // 这个应该是可用的。
    Chat->clients[c->fd] = c;
    /* We need to update the max client set if needed. */ // 如果需要更新最大客户端槽位索引。
    if (c->fd > Chat->maxclient) Chat->maxclient = c->fd;
    Chat->numclients++;
    return c;
}

/* Free a client, associated resources, and unbind it from the global
 * state in Chat. */
/* 释放一个客户端，释放相关资源，并从 Chat 中的全局状态中解绑它。 */
void freeClient(struct client *c) {
    free(c->nick);
    close(c->fd);
    Chat->clients[c->fd] = NULL;
    Chat->numclients--;
    if (Chat->maxclient == c->fd) {
        /* Ooops, this was the max client set. Let's find what is
         * the new highest slot used. */ // 糟糕，这是最大客户端槽位索引。让我们找到新的最高槽位索引。
        int j;
        for (j = Chat->maxclient-1; j >= 0; j--) {
            if (Chat->clients[j] != NULL) {
                Chat->maxclient = j;
                break;
            }
        }
        if (j == -1) Chat->maxclient = -1; // We no longer have clients. // 我们没有客户端了。
    }
    free(c);
}

/* Allocate and init the global stuff. */ // 分配并初始化全局变量。
void initChat(void) {
    Chat = chatMalloc(sizeof(*Chat));
    memset(Chat,0,sizeof(*Chat));
    /* No clients at startup, of course. */ // 程序启动时没有客户端连接。
    Chat->maxclient = -1;
    Chat->numclients = 0;

    /* Create our listening socket, bound to the given port. This
     * is where our clients will connect. */ // 创建监听套接字，绑定到指定端口。这是我们的客户端将连接的地方。
    Chat->serversock = createTCPServer(SERVER_PORT);
    if (Chat->serversock == -1) {
        perror("Creating listening socket");
        exit(1);
    }
}

/* Send the specified string to all connected clients but the one
 * having as socket descriptor 'excluded'. If you want to send something
 * to every client just set excluded to an impossible socket: -1. */ 
/*将指定的字符串发送给所有已连接的客户端，但排除具有指定套接字描述符的客户端。
如果要向所有客户端发送消息，请将 excluded 设置为不可能的套接字：-1。*/
void sendMsgToAllClientsBut(int excluded, char *s, size_t len) {
    for (int j = 0; j <= Chat->maxclient; j++) {
        if (Chat->clients[j] == NULL ||
            Chat->clients[j]->fd == excluded) continue;

        /* Important: we don't do ANY BUFFERING. We just use the kernel
         * socket buffers. If the content does not fit, we don't care.
         * This is needed in order to keep this program simple. */ 
        /*重要：我们不执行任何缓冲。我们只是使用内核套接字缓冲区。
        如果内容不适合，我们不关心。这是为了保持这个程序简单。*/
        write(Chat->clients[j]->fd,s,len);
    }
}

/* The main() function implements the main chat logic:
 * 1. Accept new clients connections if any.
 * 2. Check if any client sent us some new message.
 * 3. Send the message to all the other clients. */
/* main() 函数实现了主聊天逻辑：
 * 1. 如果有新的客户端连接请求，接受连接。
 * 2. 检查是否有客户端发送了新消息。
 * 3. 将消息发送给所有其他客户端。 */
int main(void) {
    initChat();

    while(1) {
        fd_set readfds;
        struct timeval tv;
        int retval;

        FD_ZERO(&readfds);
        /* When we want to be notified by select() that there is
         * activity? If the listening socket has pending clients to accept
         * or if any other client wrote anything. */
        /* 当我们希望 select() 通知我们有活动时？
         * 如果监听套接字有待处理的客户端连接请求，
         * 或者如果任何其他客户端发送了消息。 */
        FD_SET(Chat->serversock, &readfds);

        for (int j = 0; j <= Chat->maxclient; j++) {
            if (Chat->clients[j]) FD_SET(j, &readfds);
        }

        /* Set a timeout for select(), see later why this may be useful
         * in the future (not now). */
        /* 设置 select() 的超时时间，以后可能会有用（现在还不是）。 */
        tv.tv_sec = 1; // 1 sec timeout // 1 秒超时
        tv.tv_usec = 0;

        /* Select wants as first argument the maximum file descriptor
         * in use plus one. It can be either one of our clients or the
         * server socket itself. */
        /* Select 函数的第一个参数是使用的最大文件描述符加 1。
         * 它可以是我们的客户端之一，也可以是服务器套接字本身。 */
        int maxfd = Chat->maxclient;
        if (maxfd < Chat->serversock) maxfd = Chat->serversock;
        retval = select(maxfd+1, &readfds, NULL, NULL, &tv); // 阻塞等待文件描述符的变化。
        /* retval 可能的取值：
         * -1：select() 调用出错。
         * 0：超时时间到。
         * 大于 0：有文件描述符的变化。 */
        if (retval == -1) {
            perror("select() error");
            exit(1);
        } else if (retval) {

            /* If the listening socket is "readable", it actually means
             * there are new clients connections pending to accept. */
            /* 如果监听套接字可读，
             * 实际上意味着有新的客户端连接请求待处理。 */
            if (FD_ISSET(Chat->serversock, &readfds)) { /* 检查监听套接字是否可读。 */
                int fd = acceptClient(Chat->serversock);
                struct client *c = createClient(fd);
                /* Send a welcome message. */ /* 发送欢迎消息。 */
                char *welcome_msg =
                    "Welcome to Simple Chat! "
                    "Use /nick <nick> to set your nick.\n";
                write(c->fd,welcome_msg,strlen(welcome_msg));
                printf("Connected client fd=%d\n", fd);
            }

            /* Here for each connected client, check if there are pending
             * data the client sent us. */ 
            /* 对于每个已连接的客户端，
             * 检查客户端是否有待处理的数据。 */
            char readbuf[256];
            for (int j = 0; j <= Chat->maxclient; j++) {
                if (Chat->clients[j] == NULL) continue;
                if (FD_ISSET(j, &readfds)) {
                    /* Here we just hope that there is a well formed
                     * message waiting for us. But it is entirely possible
                     * that we read just half a message. In a normal program
                     * that is not designed to be that simple, we should try
                     * to buffer reads until the end-of-the-line is reached. */
                    /* 在这里我们假设会有一个完整的消息等待我们。
                     * 但完全有可能我们只读取了一半的消息。
                     * 在一个不那么简单的程序中，
                     * 我们应该尝试缓冲读取，直到到达行尾。 */
                    int nread = read(j,readbuf,sizeof(readbuf)-1);

                    if (nread <= 0) {
                        /* Error or short read means that the socket
                         * was closed. */
                        /* 错误或短读意味着套接字已关闭。 */
                        printf("Disconnected client fd=%d, nick=%s\n",
                            j, Chat->clients[j]->nick);
                        freeClient(Chat->clients[j]);
                    } else {
                        /* The client sent us a message. We need to
                         * relay this message to all the other clients
                         * in the chat. */
                        /* 客户端发送了一条消息。
                         * 我们需要将这条消息转发给聊天中的其他客户端。 */
                        struct client *c = Chat->clients[j];
                        readbuf[nread] = 0; // Null terminate the string. // 以空字符结尾。

                        /* If the user message starts with "/", we
                         * process it as a client command. So far
                         * only the /nick <newnick> command is implemented. */
                        /* 如果用户消息以“/”开头，
                         * 我们将其处理为客户端命令。
                         * 目前只实现了 /nick <newnick> 命令。 */
                        if (readbuf[0] == '/') {
                            /* Remove any trailing newline. */ // 移除任何尾随的换行符。
                            char *p;
                            p = strchr(readbuf,'\r'); if (p) *p = 0;
                            p = strchr(readbuf,'\n'); if (p) *p = 0;
                            /* Check for an argument of the command, after
                             * the space. */
                            /* 检查命令的参数，
                             * 即命令后的空格后的内容。 */
                            char *arg = strchr(readbuf,' ');  // 查找空格字符的位置。
                            if (arg) {
                                *arg = 0; /* Terminate command name. */ // 终止命令名称。
                                arg++; /* Argument is 1 byte after the space. */ // 参数是空格后的第一个字节。
                            }

                            if (!strcmp(readbuf,"/nick") && arg) {
                                free(c->nick);
                                int nicklen = strlen(arg);
                                c->nick = chatMalloc(nicklen+1);
                                memcpy(c->nick,arg,nicklen+1);
                            } else {
                                /* Unsupported command. Send an error. */ // 不支持的命令。发送错误消息。
                                char *errmsg = "Unsupported command\n";
                                write(c->fd,errmsg,strlen(errmsg));
                            }
                        } else {
                            /* Create a message to send everybody (and show
                             * on the server console) in the form:
                             *   nick> some message. */
                            /* 创建一条消息，
                             * 并以以下形式发送给所有客户端（并在服务器控制台显示）：
                             *   昵称> 一些消息。 */
                            char msg[256];
                            int msglen = snprintf(msg, sizeof(msg),
                                "%s> %s", c->nick, readbuf);

                            /* snprintf() return value may be larger than
                             * sizeof(msg) in case there is no room for the
                             * whole output. */
                            /* snprintf() 的返回值可能大于 sizeof(msg)，
                             * 因为没有足够的空间来存储整个输出。 */
                            if (msglen >= (int)sizeof(msg))
                                msglen = sizeof(msg)-1;
                            printf("%s",msg);

                            /* Send it to all the other clients. */
                            /* 将消息发送给所有其他客户端。 */
                            sendMsgToAllClientsBut(j,msg,msglen);
                        }
                    }
                }
            }
        } else {
            /* Timeout occurred. We don't do anything right now, but in
             * general this section can be used to wakeup periodically
             * even if there is no clients activity. */
            /* 超时发生。
             * 目前我们什么都不做，
             * 但通常这个部分可以用于周期性地唤醒，
             * 即使没有客户端活动。 */
        }
    }
    return 0;
}
